package com.wlyuan.gateway.security;


import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import reactor.core.publisher.Mono;

/**
 * @author yuanzheng
 */
@Slf4j
public class ResourceAuthenticationManager implements ReactiveAuthenticationManager {

    private final TokenStore tokenStore;

    public ResourceAuthenticationManager(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    private OAuth2AccessToken readAccessToken(String accessToken) {
        try {
            return tokenStore.readAccessToken(accessToken);
        } catch (Exception e) {
            logger.error("Read access token error: {}", accessToken, e);
            return null;
        }
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        return Mono.justOrEmpty(authentication)
                .filter(token -> token instanceof BearerTokenAuthenticationToken)
                .cast(BearerTokenAuthenticationToken.class)
                .map(BearerTokenAuthenticationToken::getToken)
                .flatMap((accessToken -> {
                    OAuth2AccessToken oAuth2AccessToken = readAccessToken(accessToken);
                    if (oAuth2AccessToken == null) {
                        logger.error("Invalid access token: {}", accessToken);
                        return Mono.error(new InvalidTokenException("Invalid access token"));
                    } else if (oAuth2AccessToken.isExpired()) {
                        logger.error("Expired access token: {}", accessToken);
                        return Mono.error(new InvalidTokenException("Expired access token"));
                    }

                    logger.info("Access token:{}", JSON.toJSONString(oAuth2AccessToken));
                    OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
                    if (oAuth2Authentication == null) {
                        logger.error("Invalid oauth2 authentication : {}", accessToken);
                        return Mono.error(new InvalidTokenException("Invalid oauth2 authentication"));
                    } else {
                        return Mono.just(oAuth2Authentication);
                    }
                })).cast(Authentication.class);
    }
}